"""
MQTT客户端模块
提供易于使用的MQTT功能接口，支持TLS加密连接和自动重连功能
"""

import paho.mqtt.client as mqtt
import logging
from typing import Optional, Callable, Any
from dataclasses import dataclass
from enum import Enum


# 配置日志
logger = logging.getLogger(__name__)


class QoSLevel(Enum):
    """MQTT QoS等级
    
    QoS（Quality of Service）等级定义了消息传递的保证级别：
    - AT_MOST_ONCE (0): 最多一次，消息可能丢失
    - AT_LEAST_ONCE (1): 至少一次，消息不会丢失但可能重复
    - EXACTLY_ONCE (2): 恰好一次，消息不会丢失且不会重复
    """
    AT_MOST_ONCE = 0   # 最多一次
    AT_LEAST_ONCE = 1  # 至少一次
    EXACTLY_ONCE = 2   # 恰好一次


@dataclass
class MQTTConfig:
    """MQTT配置类
    
    用于配置MQTT客户端连接参数，包括服务器地址、认证信息、TLS设置和重连配置
    """
    # 基本连接配置
    host: str = "ta9fe3f1.ala.cn-hangzhou.emqxsl.cn"  # MQTT服务器地址
    port: int = 8883  # MQTTS默认端口
    username: Optional[str] = None  # 用户名（可选）
    password: Optional[str] = None  # 密码（可选）
    client_id: Optional[str] = None  # 客户端ID（可选，为空时自动生成）
    keepalive: int = 60  # 心跳间隔（秒）
    clean_session: bool = True  # 是否清除会话
    
    # 自动重连配置
    automatic_reconnect: bool = True  # 是否启用自动重连
    min_retry_interval: int = 1  # 最小重试间隔（秒）
    max_retry_interval: int = 60  # 最大重试间隔（秒）
    
    # TLS/SSL加密配置
    enable_tls: bool = True  # 是否启用TLS加密
    tls_ca_cert: Optional[str] = "emqxsl-ca (1).crt"  # CA证书路径
    tls_certfile: Optional[str] = None  # 客户端证书路径（双向认证时需要）
    tls_keyfile: Optional[str] = None  # 客户端私钥路径（双向认证时需要）
    tls_insecure: bool = False  # 是否跳过证书验证（不推荐在生产环境使用）


class MQTTClient:
    """MQTT客户端类
    
    提供完整的MQTT客户端功能，包括连接管理、消息发布/订阅、TLS加密和自动重连
    """
    
    def __init__(self, config: MQTTConfig):
        """初始化MQTT客户端
        
        Args:
            config: MQTT配置对象，包含连接参数和TLS设置
        """
        self.config = config
        
        # 创建paho-mqtt客户端实例
        self.client = mqtt.Client(
            client_id=config.client_id,
            clean_session=config.clean_session
        )
        
        # 配置用户名和密码认证
        if config.username and config.password:
            self.client.username_pw_set(config.username, config.password)
        
        # 配置TLS/SSL加密连接
        if config.enable_tls:
            self._configure_tls()
        
        # 设置内部回调函数
        self.client.on_connect = self._on_connect
        self.client.on_disconnect = self._on_disconnect
        self.client.on_publish = self._on_publish
        
        # 存储用户自定义的回调函数和订阅信息
        self._user_on_message: Optional[Callable] = None  # 用户自定义消息回调
        self._subscriptions: dict = {}  # 存储订阅信息，用于重连后重新订阅
        
    def _configure_tls(self):
        """配置TLS连接
        
        根据配置设置TLS/SSL加密参数，支持单向和双向认证
        """
        try:
            # 如果指定了CA证书，则使用指定的证书配置TLS
            if self.config.tls_ca_cert:
                self.client.tls_set(
                    ca_certs=self.config.tls_ca_cert,      # CA证书路径
                    certfile=self.config.tls_certfile,     # 客户端证书路径（可选）
                    keyfile=self.config.tls_keyfile        # 客户端私钥路径（可选）
                )
            else:
                # 否则使用系统默认证书
                self.client.tls_set()
                
            # 是否跳过证书验证（仅用于测试环境）
            if self.config.tls_insecure:
                self.client.tls_insecure_set(True)
        except Exception as e:
            logger.error(f"TLS配置失败: {e}")
            raise
    def _on_connect(self, client, userdata, flags, rc):
        """连接回调函数
        
        当客户端连接到MQTT服务器时被调用
        
        Args:
            client: MQTT客户端实例
            userdata: 用户数据
            flags: 连接标志
            rc: 连接结果代码
        """
        if rc == 0:
            logger.info(f"成功连接到MQTT服务器 {self.config.host}:{self.config.port}")
            
            # 重连后重新订阅之前订阅的主题
            if self._subscriptions:
                logger.info("重新订阅之前订阅的主题")
                for topic, qos in self._subscriptions.items():
                    self.client.subscribe(topic, qos)
                    logger.info(f"重新订阅主题: {topic} (QoS: {qos})")
        else:
            logger.error(f"连接MQTT服务器失败，错误码: {rc}")
    
    def _on_disconnect(self, client, userdata, rc):
        """断开连接回调函数
        
        当客户端与MQTT服务器断开连接时被调用
        
        Args:
            client: MQTT客户端实例
            userdata: 用户数据
            rc: 断开连接结果代码（0表示正常断开）
        """
        if rc != 0:
            logger.warning(f"MQTT客户端意外断开连接，错误码: {rc}")
        else:
            logger.info("MQTT客户端已正常断开连接")
        
        # 如果启用了自动重连，paho-mqtt会自动尝试重连
        if self.config.automatic_reconnect:
            logger.info("自动重连已启用，将自动尝试重新连接")
        
    def _on_publish(self, client, userdata, mid):
        """发布消息回调函数
        
        当消息成功发布到MQTT服务器时被调用
        
        Args:
            client: MQTT客户端实例
            userdata: 用户数据
            mid: 消息ID
        """
        logger.debug(f"消息发布成功，消息ID: {mid}")
        
    def connect(self) -> bool:
        """连接到MQTT服务器
        
        建立与MQTT服务器的连接并启动消息循环
        
        Returns:
            bool: 连接是否成功
        """
        try:
            # 配置自动重连参数
            if self.config.automatic_reconnect:
                self.client.reconnect_delay_set(
                    self.config.min_retry_interval,   # 最小重试间隔
                    self.config.max_retry_interval    # 最大重试间隔
                )
            
            # 建立连接
            self.client.connect(
                self.config.host,      # 服务器地址
                self.config.port,      # 端口号
                self.config.keepalive  # 心跳间隔
            )
            
            # 启动消息循环线程
            self.client.loop_start()
            return True
        except Exception as e:
            logger.error(f"连接MQTT服务器时出错: {e}")
            return False
            
    def disconnect(self):
        """断开MQTT服务器连接
        
        停止消息循环并断开与MQTT服务器的连接
        """
        self.client.loop_stop()   # 停止消息循环
        self.client.disconnect()  # 断开连接
        
    def publish(self, topic: str, payload: Any, qos: QoSLevel = QoSLevel.AT_MOST_ONCE, retain: bool = False) -> bool:
        """发布消息到指定主题
        
        Args:
            topic: 主题名称
            payload: 消息内容（可以是字符串、字节等）
            qos: QoS等级，默认为AT_MOST_ONCE
            retain: 是否保留消息，默认为False
            
        Returns:
            bool: 发布是否成功
        """
        try:
            # 调用paho-mqtt的publish方法
            result = self.client.publish(
                topic,           # 主题
                payload,         # 消息内容
                qos.value,       # QoS等级值
                retain           # 是否保留
            )
            
            # 检查发布结果
            if result.rc == mqtt.MQTT_ERR_SUCCESS:
                logger.debug(f"消息已发布到主题: {topic}")
                return True
            else:
                logger.error(f"发布消息失败，错误码: {result.rc}")
                return False
        except Exception as e:
            logger.error(f"发布消息时出错: {e}")
            return False
            
    def subscribe(self, topic: str, qos: QoSLevel = QoSLevel.AT_MOST_ONCE, callback: Optional[Callable] = None):
        """订阅指定主题
        
        Args:
            topic: 要订阅的主题
            qos: QoS等级，默认为AT_MOST_ONCE
            callback: 消息回调函数（可选）
        """
        # 如果提供了回调函数，则设置消息处理回调
        if callback:
            self._user_on_message = callback
            self.client.on_message = self._on_message
        
        # 保存订阅信息用于重连后重新订阅
        self._subscriptions[topic] = qos.value
        
        # 执行订阅操作
        self.client.subscribe(topic, qos.value)
        
    def _on_message(self, client, userdata, msg):
        """接收消息回调函数
        
        处理接收到的MQTT消息，解码消息内容并调用用户自定义回调函数
        
        Args:
            client: MQTT客户端实例
            userdata: 用户数据
            msg: MQTT消息对象
        """
        # 尝试解码消息内容
        try:
            payload = msg.payload.decode()
        except UnicodeDecodeError:
            # 如果解码失败，使用替换字符处理
            payload = msg.payload.decode('utf-8', errors='replace')
            logger.warning(f"消息内容解码时出现错误，已使用替换字符处理: {msg.topic}")
        
        # 记录接收到的消息
        logger.debug(f"接收到消息 - 主题: {msg.topic}, 内容: {payload}, QoS: {msg.qos}")
        
        # 调用用户自定义的消息回调函数
        if self._user_on_message:
            self._user_on_message(client, userdata, msg)
            
    def set_on_message_callback(self, callback: Callable):
        """设置消息接收回调函数
        
        设置处理接收到的MQTT消息的回调函数
        
        Args:
            callback: 消息回调函数，签名应为 callback(client, userdata, msg)
        """
        self._user_on_message = callback
        self.client.on_message = self._on_message
        
    @property
    def is_connected(self) -> bool:
        """检查客户端是否已连接
        
        Returns:
            bool: 客户端是否已连接到MQTT服务器
        """
        return self.client.is_connected()